package com.my.reggie.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson;
import com.my.reggie.common.JacksonObjectMapper;
import com.my.reggie.common.RabbitmqDelayProducer;
import com.my.reggie.config.DelayMessageConfig;
import com.my.reggie.config.WxPayConfig;
import com.my.reggie.enums.wxpay.WxApiType;
import com.my.reggie.enums.wxpay.WxNotifyType;
import com.my.reggie.enums.wxpay.WxRefundStatus;
import com.my.reggie.enums.wxpay.WxTradeState;
import com.my.reggie.pojo.*;
import com.my.reggie.service.*;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;


@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
    @Autowired
    private ShoppingCartService shoppingCartService;
    @Autowired
    private OrdersService ordersService;
    @Autowired
    private PaymentInfoService paymentInfoService;
    @Resource
    private WxPayConfig wxPayConfig;
    @Resource
    private CloseableHttpClient wxPayClient;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private RabbitmqDelayProducer delayProducer;

    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public Map nativePay(Orders orders, HttpSession session) throws IOException {
        //获取用户id
        Long userId = (Long) session.getAttribute("user");
        //构建返回体Map
        Map<String,String> map = new HashMap<>();
        //获取当前用户购物车数据
        LambdaQueryWrapper<ShoppingCart> SCLqw = new LambdaQueryWrapper<>();
        SCLqw.eq(ShoppingCart::getUserId,userId);
        List<ShoppingCart> shoppingCartList = shoppingCartService.list(SCLqw);
        //尝试获取订单号，若为空则表示这是通过“去支付”按钮发起的支付请求，否则为通过“我的订单”处发起请求，此时订单号已生成
        String number = orders.getNumber();
        //创建Order实体
        Orders order = new Orders();
        /*加入购物车判断的目的是防止获取到上一次支付的支付信息，假如购物车为空说明该笔支付重新生成了支付信息
        因为只有重新生成订单才会清空购物车*/
        if(shoppingCartList.size() == 0 && number == null) {
            //获取redis缓存
            String code_url = (String) redisTemplate.opsForValue().get("codeUrl");
            //获取订单id
            String order_id = (String) redisTemplate.opsForValue().get("orderId");
            //支付链接还未过期，直接返回
            if(code_url != null && order_id != null){
                map.put("codeUrl",code_url);
                map.put("orderId",order_id);
                return map;
            }
            if(order_id == null && code_url != null) {
                //订单超时未支付
                map.put("message","timeout");
                return map;
            }
        }
        if(number != null) {
            //表示该请求是在“我的订单”页面发起的支付请求，此时该订单已经被创建
            String key = "orderNo_" + number + "_codeUrl";
            String codeUrl = (String) redisTemplate.opsForValue().get(key);
            if(codeUrl != null) {
                map.put("codeUrl",codeUrl);
                map.put("orderId",number);
                return map;
            }
            order = orders;
        } else {
            order = ordersService.createOrder(orders, session);
        }

        //存储订单id，存活时间为5分钟
        redisTemplate.opsForValue().set("orderId",order.getNumber(),5,TimeUnit.MINUTES);
        map.put("orderId",order.getNumber());
        //计算金额
        log.info("计算支付金额...");
        LambdaQueryWrapper<Orders> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Orders::getNumber,order.getNumber());
        Orders order_getAmount = ordersService.getOne(lqw);
        BigDecimal amount = order_getAmount.getAmount();
        int PayAmount = amount.intValue();

        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid",wxPayConfig.getMchId())
                .put("appid", wxPayConfig.getAppid())
                .put("description", "外卖订单")
                .put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()))
                .put("out_trade_no", orders.getNumber());  //订单号
        rootNode.putObject("amount")
                .put("total",PayAmount );  //金额

        objectMapper.writeValue(bos, rootNode);

        httpPost.setEntity(new StringEntity(bos.toString(StandardCharsets.UTF_8), "UTF-8"));
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        String bodyAsString = EntityUtils.toString(response.getEntity());
        JSONObject body = JSON.parseObject(bodyAsString);
        String code_url = (String) body.get("code_url");
        map.put("codeUrl",code_url);
        //将Code_url存入redis缓存，有效期为2小时
        redisTemplate.opsForValue().set("codeUrl",code_url,2, TimeUnit.HOURS);
        redisTemplate.opsForValue().set("orderNo_"+order.getNumber()+"_codeUrl",code_url,2, TimeUnit.HOURS);
        log.info("Code_url===>{}",code_url);
        return map;
    }

    /**
     * 处理订单
     * @param notification
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processOrder(Notification notification) {
        String decryptData = notification.getDecryptData();
        JSONObject data = JSON.parseObject(decryptData);
        //获取订单号
        String orderNumber = (String) data.get("out_trade_no");
        //获取支付状态
        String tradeState = (String) data.get("trade_state");
        /*在对业务数据进行状态检查和处理之前，
        要采用数据锁进行并发控制，
        以避免函数重入造成的数据混乱*/
        //尝试获取锁：
        // 成功获取则立即返回true，获取失败则立即返回false。不必一直等待锁的释放
        if(lock.tryLock()) {
            try {
                //处理重复通知
                //接口调用的幂等性：无论接口被调用多少次，产生的结果是一致的
                Integer status = ordersService.getOrderStatus(orderNumber);
                if(status == null || status == 2) {   //该订单已经支付
                    log.info("订单已被处理");
                    return;
                }
                //更新订单状态
                ordersService.updateStatusByOrderNo(orderNumber,tradeState);

                //记录日志
                paymentInfoService.saveInfo(notification.getDecryptData());
            } finally {
                //主动释放锁
                lock.unlock();
            }
        }
    }

    /**
     * 取消订单
     * @param order
     */
    @Override
    public void cancelOrder(Orders order) throws Exception {
        //调用微信支付的关单接口
        this.closeOrder(order.getNumber());
        //更新订单状态
        Orders order_temp = ordersService.getById(order.getId());
        ordersService.updateStatusByOrderNo(order_temp.getNumber(),"5");
    }

    /**
     * 查询订单
     * @param orderNo
     * @return
     */
    @Override
    public String queryOrder(String orderNo) throws Exception {
        log.info("查单接口调用===>{}",orderNo);
        //构建请求链接
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept","application/json");

        CloseableHttpResponse response = wxPayClient.execute(httpGet);

        return EntityUtils.toString(response.getEntity());
    }

    /**
     * 商户主动查询订单状态
     * 当核实到订单超时未支付则取消订单
     * 当核实到订单已支付则更新订单状态
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkOrderStatus(String orderNo) throws Exception {
        log.info("根据订单号核实订单状态==>{}",orderNo);

        log.info("在数据库中查询订单状态....");
        Integer status = ordersService.getOrderStatus(orderNo);

        //数据库中该订单已删除(防止出现空指针)
        if(status == null) {
            log.info("数据库中查询不到关于单号:{}的信息",orderNo);
            return;
        }
        if(status != 1) {
            //订单不是”未支付“状态
            log.info("订单不是”未支付“状态，无需进行进一步处理");
            return;
        }

        String result = this.queryOrder(orderNo);
        Gson gson = new Gson();
        Map<String,String> map = gson.fromJson(result, HashMap.class);

        //获取订单状态
        String tradeState = map.get("trade_state");

        //判断订单状态
        if(WxTradeState.NOTPAY.getType().equals(tradeState)) {
            log.info("核实到订单超时未支付==>{}",orderNo);

            //关闭订单
            log.info("订单已自动取消");
            this.closeOrder(orderNo);

            //更新本地订单状态
            ordersService.updateStatusByOrderNo(orderNo,"5");
        }

        if(WxTradeState.SUCCESS.getType().equals(tradeState)) {
            log.info("核实到订单已支付==>{}",orderNo);

            Integer orderStatus = ordersService.getOrderStatus(orderNo);
            if(orderStatus != null && orderStatus != 2) {
                //更新本地订单状态
                ordersService.updateStatusByOrderNo(orderNo,"2");

                //保存订单记录
                paymentInfoService.saveInfo(result);
            }
        }

        if(WxTradeState.CLOSED.getType().equals(tradeState)) {
            log.info("核实到订单已取消==>{}",orderNo);

            Integer orderStatus = ordersService.getOrderStatus(orderNo);
            if(orderStatus != null && orderStatus != 5) {
                //更新本地订单状态
                ordersService.updateStatusByOrderNo(orderNo,"5");

                //保存订单记录
                paymentInfoService.saveInfo(result);
            }
        }
    }

    /**
     * 关单接口的调用
     * @param orderNo
     */
    @Override
    public void closeOrder(String orderNo) throws Exception {
        log.info("关单接口的调用，订单号 ===> {}", orderNo);

        //创建远程请求对象
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url);
        HttpPost httpPost = new HttpPost(url);

        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid",wxPayConfig.getMchId());

        objectMapper.writeValue(bos, rootNode);

        httpPost.setEntity(new StringEntity(bos.toString(StandardCharsets.UTF_8), "UTF-8"));

        //完成签名并执行请求
        try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
            String jsonString = JSON.toJSONString(response);
            JSONObject body = JSON.parseObject(jsonString);

            JSONObject statusLine = body.getJSONObject("statusLine");
            String code = statusLine.getString("statusCode");

            log.info("关闭订单成功，返回状态码:{}",code);
        }
    }

    /**
     * 退款接口API
     * @param order
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(Orders order) throws Exception {
        log.info("调用退款接口API");

        //调用统一退款API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept","application/json");
        httpPost.addHeader("Content-type","application/json;charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new JacksonObjectMapper();

        ObjectNode rootNode = objectMapper.createObjectNode();
        String refundNumber = null;
        Orders order_temp = ordersService.getById(order.getId());
        if(order_temp.getRefundNumber() != null) {
            refundNumber = order_temp.getRefundNumber();
        } else {
            refundNumber = String.valueOf(IdWorker.getId());

            //记录订单退款编号及退款时间
            LambdaUpdateWrapper<Orders> luw = new LambdaUpdateWrapper<>();
            luw.eq(Orders::getNumber,order.getNumber());
            luw.set(Orders::getRefundNumber,refundNumber);
            luw.set(Orders::getRefundTime, LocalDateTime.now());
            ordersService.update(luw);
        }

        rootNode.put("out_trade_no",order.getNumber())
                .put("out_refund_no", refundNumber)
                .put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));
        rootNode.putObject("amount")
                .put("refund",order.getAmount())
                .put("total",order.getAmount())
                .put("currency","CNY");

        objectMapper.writeValue(bos,rootNode);

        httpPost.setEntity(new StringEntity(bos.toString(StandardCharsets.UTF_8),"UTF-8"));
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        String bodyAsString = EntityUtils.toString(response.getEntity());

        JSONObject body = JSON.parseObject(bodyAsString);
        String refundStatus = (String) body.get("status");

        if(WxRefundStatus.PROCESSING.getType().equals(refundStatus)) {
            //退款处理中
            log.info("退款处理中...");
            //更新订单状态
            ordersService.updateStatusByOrderNo(order.getNumber(),refundStatus);
            //更新日志信息
            paymentInfoService.updateStatus(order.getNumber(),refundStatus);

            //将退款编号存入退款延迟队列，延迟时间为5分钟
            log.info("退款编号：{}进入延迟队列...",refundNumber);
            delayProducer.publish(refundNumber, order.getNumber(),
                    DelayMessageConfig.DELAY_EXCHANGE_NAME,DelayMessageConfig.ROUTING_KEY_REFUND,1000*60*5);
        }
        if (WxRefundStatus.SUCCESS.getType().equals(refundStatus)){
            //退款处理成功
            log.info("退款处理成功");
            //更新订单状态
            ordersService.updateStatusByOrderNo(order.getNumber(),"REFUND_SUCCESS");
            //更新日志信息
            paymentInfoService.updateStatus(order.getNumber(),"REFUND_SUCCESS");
        }
        if (WxRefundStatus.ABNORMAL.getType().equals(refundStatus)){
            //退款处理异常
            log.warn("退款处理异常");
            //更新订单状态
            ordersService.updateStatusByOrderNo(order.getNumber(),refundStatus);
            //更新日志信息
            paymentInfoService.updateStatus(order.getNumber(),"REFUND_ABNORMAL");
        }
    }

    /**
     * 处理退款通知结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processRefund(Notification notification) {
        String decryptData = notification.getDecryptData();
        JSONObject data = JSON.parseObject(decryptData);
        //获取订单号
        String orderNumber = (String) data.get("out_trade_no");
        //获取支付状态
        String refundState = (String) data.get("refund_status");
        /*在对业务数据进行状态检查和处理之前，
        要采用数据锁进行并发控制，
        以避免函数重入造成的数据混乱*/
        //尝试获取锁：
        // 成功获取则立即返回true，获取失败则立即返回false。不必一直等待锁的释放
        if(lock.tryLock()) {
            try {
                //处理重复通知
                //接口调用的幂等性：无论接口被调用多少次，产生的结果是一致的
                Integer status = ordersService.getOrderStatus(orderNumber);
                if(status == null || status == 7) {   //该订单已经退款成功
                    log.info("该退款已处理完成");
                    return;
                }
                if(WxRefundStatus.SUCCESS.getType().equals(refundState)) {
                    //更新订单状态
                    log.info("退款结果回调成功，退款结果为SUCCESS...");
                    ordersService.updateStatusByOrderNo(orderNumber,"REFUND_SUCCESS");

                    //记录日志
                    paymentInfoService.updateStatus(orderNumber,"REFUND_SUCCESS");
                }
                else {
                    //更新订单状态
                    log.info("退款结果回调成功，回调结果不为SUCCESS...");
                    ordersService.updateStatusByOrderNo(orderNumber,refundState);

                    //记录日志
                    paymentInfoService.updateStatus(orderNumber,refundState);
                }
            } finally {
                //主动释放锁
                lock.unlock();
            }
        }
    }

    @Override
    public String queryRefund(String refundNo) throws Exception {
        log.info("查询退款接口的调用==>{}", refundNo);

        //构建请求链接
        String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(),refundNo);
        url = wxPayConfig.getDomain().concat(url);

        URIBuilder uriBuilder = new URIBuilder(url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept","application/json");

        CloseableHttpResponse response = wxPayClient.execute(httpGet);

        return EntityUtils.toString(response.getEntity());
    }

    /**
     * 查询订单退款状态
     * 如果超过5分钟未退款成功转入退款异常
     * 如果已经退款成功则更新订单状态
     * @param refundNo
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkRefundStatus(String refundNo) throws Exception {
        log.info("根据退款编号核实退款状态==>{}",refundNo);

        log.info("在数据库中查询订单退款状态...");
        LambdaQueryWrapper<Orders> lqw0 = new LambdaQueryWrapper<>();
        lqw0.eq(refundNo != null,Orders::getRefundNumber,refundNo);
        Orders order_temp = ordersService.getOne(lqw0);
        Integer status = order_temp.getStatus();
        if(status != 6) {
            log.info("订单状态不是”退款中“，无需进一步处理");
            return;
        }

        String result = this.queryRefund(refundNo);
        JSONObject resultJson = JSON.parseObject(result);
        String refundStatus = (String) resultJson.get("status");

        //获取订单编号
        LambdaUpdateWrapper<Orders> lqw = new LambdaUpdateWrapper<>();
        lqw.eq(Orders::getRefundNumber,refundNo);
        Orders order = ordersService.getOne(lqw);
        String orderNo = order.getNumber();

        //判断订单状态
        if(WxRefundStatus.SUCCESS.getType().equals(refundStatus)) {
            log.warn("核实到订单已退款成功，订单号==>{}",orderNo);

            //更新订单状态
            ordersService.updateStatusByOrderNo(orderNo,"REFUND_SUCCESS");

            //更新支付日志
            paymentInfoService.updateStatus(orderNo,"REFUND_SUCCESS");
        }
        else if(WxRefundStatus.PROCESSING.getType().equals(refundStatus)) {
            log.info("退款正在处理中，退款编号==>{}",refundNo);
            //将退款消息存入退款延迟队列，5分钟之后再次查询
            delayProducer.publish(order.getRefundNumber(),order.getRefundNumber(),
                    DelayMessageConfig.DELAY_EXCHANGE_NAME,DelayMessageConfig.ROUTING_KEY_REFUND,1000*60*5);
        }
        else{
            log.warn("核实到订单退款异常，订单号==>{}",orderNo);

            if(!WxRefundStatus.ABNORMAL.getType().equals(refundStatus)){
                log.info("更新订单信息及日志...");
                //更新订单状态
                ordersService.updateStatusByOrderNo(orderNo,WxRefundStatus.ABNORMAL.getType());

                //更新支付日志
                paymentInfoService.updateStatus(orderNo,WxRefundStatus.ABNORMAL.getType());
            }
        }
    }
}
